package com.zy.common.utils;

import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.springframework.http.HttpStatus;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * OkHttp工具类
 *
 * @author huxingbin
 */
@Slf4j
public class OkHttpUtils {

    public final static int READ_TIMEOUT = 10;

    public final static int CONNECT_TIMEOUT = 10;

    public final static int WRITE_TIMEOUT = 10;

    private static final MediaType JSON_TYPE = MediaType.parse("application/json; charset=utf-8");

    private static final MediaType FORM_DATA_TYPE = MediaType.parse("application/x-www-form-urlencoded; charset=utf-8");

    public static final MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse("Content-Type: binary/octet-stream");

    private OkHttpClient mOkHttpClient;

    private static OkHttpUtils mInstance;

    private static final byte[] LOCKER = new byte[0];

    private OkHttpUtils() {
        mOkHttpClient = new OkHttpClient().newBuilder()
                //10秒读取超时
                .readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
                //10秒连接超时
                .connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
                //10m秒写入超时
                .writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS)
                .retryOnConnectionFailure(true)
                .build();
    }

    public static OkHttpUtils getInstance() {
        if (mInstance == null) {
            synchronized (LOCKER) {
                if (mInstance == null) {
                    mInstance = new OkHttpUtils();
                }
            }
        }
        return mInstance;
    }

    /**
     * 同步get请求
     *
     * @param url 地址
     * @return Response 返回数据
     */
    public OkHttpResp getSync(final String url) throws IOException {
        final Request request = new Request.Builder().url(url).build();
        final Call call = mOkHttpClient.newCall(request);
        return fromResp(call.execute(), StandardCharsets.UTF_8);
    }

    /**
     * post同步请求，提交Json数据
     *
     * @param url  地址
     * @param json json格式的字符串
     * @return Response
     */
    public OkHttpResp postSyncJson(String url, String json) throws IOException {
        final RequestBody requestBody = RequestBody.create(JSON_TYPE, json);
        final Request request = new Request.Builder().url(url).post(requestBody).build();
        return fromResp(mOkHttpClient.newCall(request).execute(), StandardCharsets.UTF_8);
    }

    /**
     * post同步请求，提交Json数据
     *
     * @param url  地址
     * @param json json格式的字符串
     * @return Response
     */
    public OkHttpResp postSyncJson(String url, String json, Map<String, String> header) throws IOException {
        final RequestBody requestBody = RequestBody.create(JSON_TYPE, json);
        Request.Builder builder = new Request.Builder();
        header.forEach(builder::addHeader);
        final Request request = builder.url(url).post(requestBody).build();
        return fromResp(mOkHttpClient.newCall(request).execute(), StandardCharsets.UTF_8);
    }

    /**
     * post同步请求，提交表单数据
     *
     * @param url
     * @param formData
     * @param header
     * @return
     * @throws IOException
     */
    public OkHttpResp postSync(String url, String formData, Map<String, String> header) throws IOException {
        final RequestBody requestBody = RequestBody.create(FORM_DATA_TYPE, formData);
        Request.Builder builder = new Request.Builder();
        header.forEach(builder::addHeader);
        final Request request = builder.url(url).post(requestBody).build();
        return fromResp(mOkHttpClient.newCall(request).execute(), StandardCharsets.UTF_8);
    }

    /**
     * 同步基于post的文件上传
     *
     * @param url     地址
     *                file    提交的文件
     * @param fileKey 提交的文件key
     * @return Response
     * @throws IOException
     */
    public OkHttpResp uploadSync(String url, String filepath, String fileKey) throws IOException {
        File file = new File(filepath);
        Request request = new Request.Builder()
                .url(url)
                .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))
                .build();
        return fromResp(mOkHttpClient.newCall(request).execute(), StandardCharsets.UTF_8);
    }

    /**
     * 同步基于post的文件上传
     *
     * @param url   地址
     *              file    提交的文件
     * @param bytes
     * @return Response
     * @throws IOException
     */
    public OkHttpResp uploadImage(String url, byte[] bytes) throws IOException {
        Request request = new Request.Builder()
                .url(url)
                .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, bytes))
                .build();
        return fromResp(mOkHttpClient.newCall(request).execute(), StandardCharsets.UTF_8);
    }

    /**
     * 同步基于post的文件下载
     *
     * @param fileUrl 下载地址
     * @return Response
     * @throws IOException
     */
    public OkHttpResp downLoadFile(String fileUrl) throws IOException {
        Request request = new Request.Builder()
                .url(fileUrl)
                .build();
        return fromResp(mOkHttpClient.newCall(request).execute(), StandardCharsets.UTF_8);
    }

    /**
     * 获取请求结果对象
     *
     * @param response response
     * @param charset  StandardCharsets
     * @return 请求结果对象
     * @throws IOException IO异常
     */
    public OkHttpResp fromResp(Response response, Charset charset) throws IOException {
        if (HttpStatus.OK.value() != response.code()) {
            return OkHttpResp.builder().code(response.code()).msg(response.message()).build();
        } else {
            byte[] bytes = response.body().bytes();
            String result = new String(bytes, charset);
            return OkHttpResp.from(HttpStatus.OK, result);
        }
    }


    /**
     * post同步请求，提交byte[]数据
     *
     * @param url   地址
     * @param bytes 文件对应的byte[]
     * @return Response
     */
    public Response postSyncByteArray(String url, byte[] bytes, Map<String, String> header) throws IOException {
        final RequestBody requestBody = RequestBody.create(MediaType.parse("application/octet-stream"), bytes);
        Request.Builder builder = new Request.Builder();
        header.forEach(builder::addHeader);
        final Request request = builder.url(url).post(requestBody).build();
        return mOkHttpClient.newCall(request).execute();
    }

    /**
     * get同步请求
     *
     * @param url
     * @param header
     * @return
     * @throws IOException
     */
    public OkHttpResp getSyncJson(String url, Map<String, String> header) throws IOException {
        Request.Builder builder = new Request.Builder();
        header.forEach(builder::addHeader);
        final Request request = builder.url(url).get().build();
        return fromResp(mOkHttpClient.newCall(request).execute(), StandardCharsets.UTF_8);
    }

    /**
     * delete同步请求
     *
     * @param url
     * @param header
     * @return
     * @throws IOException
     */
    public OkHttpResp deleteSyncJson(String url, String json, Map<String, String> header) throws IOException {
        final RequestBody requestBody = RequestBody.create(JSON_TYPE, json);
        Request.Builder builder = new Request.Builder();
        header.forEach(builder::addHeader);
        final Request request = builder.url(url).delete(requestBody).build();
        return fromResp(mOkHttpClient.newCall(request).execute(), StandardCharsets.UTF_8);
    }


    /**
     * 向指定 URL 发送GET方法的请求
     *
     * @param url         发送请求的 URL
     * @param param       请求参数，请求参数应该是 name1=value1&name2=value2 的形式。
     * @param contentType 编码类型
     * @return 所代表远程资源的响应结果
     */
    public static String sendGet(String url, String param, String contentType) {
        StringBuilder result = new StringBuilder();
        BufferedReader in = null;
        try {
            String urlNameString = StringUtils.isNotBlank(param) ? url + "?" + param : url;
            log.info("sendGet - {}", urlNameString);
            URL realUrl = new URL(urlNameString);
            URLConnection connection = realUrl.openConnection();
            connection.setRequestProperty("accept", "*/*");
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            connection.connect();
            in = new BufferedReader(new InputStreamReader(connection.getInputStream(), contentType));
            String line;
            while ((line = in.readLine()) != null) {
                result.append(line);
            }
            log.info("recv - {}", result);
        } catch (ConnectException e) {
            log.error("调用HttpUtils.sendGet ConnectException, url=" + url + ",param=" + param, e);
        } catch (SocketTimeoutException e) {
            log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e);
        } catch (IOException e) {
            log.error("调用HttpUtils.sendGet IOException, url=" + url + ",param=" + param, e);
        } catch (Exception e) {
            log.error("调用HttpsUtil.sendGet Exception, url=" + url + ",param=" + param, e);
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (Exception ex) {
                log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
            }
        }
        return result.toString();
    }
}
